﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace OpenGameKit
{
    public class Scene
    {
        /// <summary>
        /// Set the top left x location of the view.
        /// </summary>
        public int ViewX = 0;
        /// <summary>
        /// Set the top left y locaton of the view.
        /// </summary>
        public int ViewY = 0;
        /// <summary>
        /// Use this to set how much of the scene is visible on the screen.
        /// </summary>
        public int ViewWidth { get; set; }
        /// <summary>
        /// Use this to set how much of the scene is visible on the screen.
        /// </summary>
        public int ViewHeight { get; set; }

        internal List<Sprite> SceneSprites = null;
        private int[,] SceneTiles = new int[64, 48];
        /// <summary>
        /// Use this to set what tile sheet to load the tiles from.
        /// </summary>
        public TileSheet CurrentTileSheet = null;
        public Color BackColor = Color.Black;

        // This event can be used if you want to draw graphics only visible in one scene.
        public event Game.GameEventHandler OnRender;

        /// <summary>
        /// Create the scene.
        /// </summary>
        /// <param name="viewWidth">How much of the scene is visible on the screen.</param>
        /// <param name="viewHeight">How much of the scene is visible on the screen.</param>
        public Scene(int viewWidth, int viewHeight)
        {
            ViewWidth = viewWidth;
            ViewHeight = viewHeight;
            SceneSprites = new List<Sprite>();

            for (int x = 0; x < SceneTiles.GetUpperBound(0); x++)
            {
                for (int y = 0; y < SceneTiles.GetUpperBound(1); y++)
                {
                    SceneTiles[x, y] = -1;
                }
            }

            OnRender += Scene_OnRender;
        }

        private void Scene_OnRender(object sender, GameEventArgs e)
        {
        }

        /// <summary>
        /// Add a sprite to the scene.
        /// </summary>
        /// <param name="s">The sprite to add to the scene.</param>
        public void AddSprite(Sprite s)
        {
            SceneSprites.Add(s);
        }

        /// <summary>
        /// Remove a sprite from the scene.
        /// </summary>
        /// <param name="s"></param>
        public void RemoveSprite(Sprite s)
        {
            SceneSprites.Remove(s);
        }

        /// <summary>
        /// Set a tile to one of those you defined in your tile sheet, set id to -1 for a empty tile.
        /// </summary>
        /// <param name="x">The x grid to draw on.</param>
        /// <param name="y">The y grid to draw on.</param>
        /// <param name="id"></param>
        public void SetTile(int x, int y, int id)
        {
            SceneTiles[x, y] = id;
        }

        /// <summary>
        /// Returns the tile id of the given grid location, -1 is a empty tile.
        /// </summary>
        /// <param name="x">The x grid.</param>
        /// <param name="y">The y grid.</param>
        /// <returns></returns>
        public int GetTile(int x, int y)
        {
            return SceneTiles[x, y];
        }

        /// <summary>
        /// Sets every tile to a given tile. Set to -1 for a empty tile.
        /// </summary>
        /// <param name="id">The tile's id given to it when the tile was defined.</param>
        public void FillTiles(int id)
        {
            for (int x = 0; x < SceneTiles.GetUpperBound(0); x++)
            {
                for (int y = 0; y < SceneTiles.GetUpperBound(1); y++)
                {
                    SceneTiles[x, y] = id;
                }
            }
        }

        /// <summary>
        /// Resize the number of tile slots on the grid to draw onto the screen. 
        /// Tile drawing is slow use as few tiles as you can.
        /// </summary>
        /// <param name="tilesX">The amount of tiles across.</param>
        /// <param name="tilesY">The amount of tiles down.</param>
        public void SetTileGridSize(int tilesX, int tilesY)
        {
            int[,] T = SceneTiles;

            SceneTiles = new int[tilesX, tilesY];

            for (int x = 0; x < T.GetUpperBound(0); x++)
            {
                for (int y = 0; y < T.GetUpperBound(1); y++)
                {
                    if (x <= SceneTiles.GetUpperBound(0))
                    {
                        if (y <= SceneTiles.GetUpperBound(1))
                            SceneTiles[x, y] = T[x, y];
                    }
                }
            }
        }

        /// <summary>
        /// Sets the view to the default view.
        /// </summary>
        public void ResetView()
        {
            ViewX = 0;
            ViewY = 0;
            ViewWidth = Game.Width;
            ViewHeight = Game.Height;
        }

        /// <summary>
        /// Sets the center of the view on given x and y location.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void FocusView(int x, int y)
        {
            // Need to account for the fact the ViewX and ViewY are not in the center.
            ViewX = x - (ViewWidth / 2);
            ViewY = y - (ViewHeight / 2);
        }

        /// <summary>
        /// Returns the location of the mouse in the scene and not the window.
        /// </summary>
        /// <returns></returns>
        public int GetMouseSceneX()
        {
            return Game.GetMouseX() + ViewX;
        }

        /// <summary>
        /// Returns the location of the mouse in the scene and not the window.
        /// </summary>
        /// <returns></returns>
        public int GetMouseSceneY()
        {
            return Game.GetMouseY() + ViewY;
        }

        /// <summary>
        /// Run a Python script file, can also be used to load maps.
        /// </summary>
        /// <param name="file">The file to run.</param>
        public void RunScript(string file)
        {
            RunScript(file, this);
        }

        /// <summary>
        /// Runs a Python script file, can also be used to load maps.
        /// </summary>
        /// <param name="file">The file to run.</param>
        /// <param name="sender">The sender object.</param>
        public void RunScript(string file, object sender)
        {
            Game.RunScript(file, sender);
        }

        /// <summary>
        /// Meant for use in python scripts. You can ignore this if you are not scripting.
        /// </summary>
        /// <param name="file"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        public TileSheet CreateTileSheet(string file, int width, int height, Color transparent)
        {
            return new TileSheet(file, width, height, transparent);
        }

        internal void Update()
        {
            var SpriteLayers = from Sprite in SceneSprites
                               orderby Sprite.Layer
                               select Sprite;

            foreach (Sprite s in SpriteLayers)
            {
                s.Update();
            }
        }

        internal void Render()
        {
            for (int x = 0; x < SceneTiles.GetUpperBound(0); x++)
            {
                for (int y = 0; y < SceneTiles.GetUpperBound(1); y++)
                {
                    // Check to see if the tile grid is empty.
                    if (SceneTiles[x, y] != -1)
                    {
                        Graphics.DrawImage(CurrentTileSheet.Tiles[SceneTiles[x, y]], 
                            x * CurrentTileSheet.TileWidth, y * CurrentTileSheet.TileHeight, 
                            CurrentTileSheet.TileWidth, CurrentTileSheet.TileHeight);
                    }
                }
            }

            // Order the sprites by layer.
            var SpriteLayers = from Sprite in SceneSprites
                               orderby Sprite.Layer
                               select Sprite;

            foreach (Sprite s in SpriteLayers)
            {
                // Render the sprite to the set layer.
                s.Render();
            }

            // Let the user do his rendering.
            OnRender(this, new GameEventArgs());
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }
}
